/********************************************************************************************************************************
 * 《深入Linux设备驱动程序内核机制》-- 陈学松 著，电子工业出版社， 2012年1月第1次印刷
 * Date: 2024-04-23 16:16
 * Author: dimon.chen 
 * EMali: 1181302388@qq.com 
 * 
 * 章节: 第二章 内核内存管理
 *       
 * 
 ********************************************************************************************************************************/

//Linux 内核模块总是应该包含着3个头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/percpu-defs.h>
#include <linux/percpu.h>
#include <asm/sections.h>

// percpu.h 头文件包含关系
// <linux/percpu.h> -> 包含 <linux/asm-generic/percpu.h> -> 包含 <linux/percpu-defs.h>
//
// per-CPU 变量使用宏 DECLARE_PER_CPU(type,name) 声明一个内核 per-CPU 变量
// per-CPU 变零使用宏 DEFINE_PER_CPU(type,name) 定义一个内核 per-CPU 变量
// 宏 DECLARE_PER_CPU(type, name) ， DEFINE_PER_CPU(type, name) 定义在头文件 <linux/percpu-defs.h>
// #define DECLARE_PER_CPU(type,name)
//          extern __attribute__((section(".data..percpu"))) __typeof__(type) name;
// 
// #define DEFINE_PER_CPU(type,name)
//          __attribute__((section(".data..percpu"))) __typeof__(type) name;
//
// 可以看到 DEFINE_PER_CPU(type,name) 宏声明的 per-CPU 变量存放在 ".data..percpu" section，除此之外
// 和普通的变量声明没有特别的地方。
//
// 链接器脚本 <asm/x86/kernel/vmlinux.lds.S> 包含 <include/asm-generic/vmlinux.lds.h>，考察内核
// 链接器脚本发现在内核链接时
// .=ALIGN(1<<12)
// .data..percpu : AT(ADDR(.data..percpu) - 0xc0000000)
// {
//     __per_cpu_load = .;
//     __per_cpu_start = .;
//     *(.data..percpu..first)
//     *(.data..percpu..page_aligned)
//     *(.data..percpu)
//     *(.data..percpu..shared_aligned)
//     __per_cpu_end = .;
// }
// .=ALIGN(1<<12)
//
// 为了配合在 vmlinux.lds 脚本中定义的 __per_cpu_load, __per_cpu_start, __per_cpu_end 变量
// 内核源码在 <include/asm-generic/sections.h> 中声明了如下的外部变量
// char __per_cpu_load[], __per_cpu_start[], __per_cpu_end[]
//
//


DEFINE_PER_CPU(int, percpu_dimon);


struct birth{
    int year;
    int month;
    int day;
};

DEFINE_PER_CPU(struct birth, my_birthday) = {1,1,1000};


int my_percpu_init(void)
{
    printk("my_percpu_init() init\n");



    printk("include/asm-generic/sections.h\n");
    printk("per-CPU vmlinux.lds variable\n");


    // 内核链接的时候可以找到，但是在模块module中不能链接到
    //printk("__per_cpu_load = %p\n", __per_cpu_load);
    //printk("__per_cpu_start = %p\n", __per_cpu_start);
    //printk("__per_cpu_end = %p\n", __per_cpu_end);

    int i = 0;
    for(i=0; i< 100000; i++){
        get_cpu_var(my_birthday).year++;
        put_cpu_var(my_birthday);
    }

    int cpu0 = 0;
    int cpu1 = 0;

    //正确的宏名字是 raw_processor_id() , 是处理器 processor （加上 or)
    //不是 raw_process_id(), 单词拼写错误了，少了 process-or 两个字母(or)
    //
    i = 0;
    while(i < 100 && !(cpu0==1 && cpu1==1)){
        i++;
        printk("cpuid=%d\n", raw_smp_processor_id());

        if(raw_smp_processor_id() == 0 && !cpu0){
            printk("cpu per-CPU variable birthday: %d\n", get_cpu_var(my_birthday).year);
            put_cpu_var(my_birthday);
            cpu0 = 1;
        }
        
        if(raw_smp_processor_id() == 1 && !cpu1){
            printk("cpu0 per-CPU variable birthday: %d\n", get_cpu_var(my_birthday).year);
            put_cpu_var(my_birthday);
            cpu1 = 1;
        }
    }

    //
    //访问静态 per-CPU 变量，应该使用宏 get_cpu_var()，因为在 get_cpu_var() 宏中调用了
    //preempt_disable() 关闭内核调度器可抢占，所以调用 get_cpu_var() 之后必须要调用配对的
    //put_cpu_var() 来使能内核调度器可抢占特性(preempt_enable()
    //
    //

    // get_cpu_var 宏定义，的替换等价于如下形式，实际的内核源码宏定义比较复杂
    // #define get_cpu_var(var) ({
    //      preempt_disable();
    //      (typeof(var)*)(&(var) + __per_cpu_offset[raw_smp_process_id()]);
    //    })
    //

    // put_cpu_var 宏定义
    // #define put_cpu_var(var) \
    //      do{ 
    //          (void)&(var);
    //          preempt_enable();
    //       } while(0)
    //


    printk("cpu0 &my_birthday=%p\n", &per_cpu(my_birthday, 0));
    printk("cpu1 &my_birthday=%p\n", &per_cpu(my_birthday, 1));
    printk("current cpu-%d &my_birthday=%p\n", raw_smp_processor_id(), &get_cpu_var(my_birthday));
    put_cpu_var(my_birthday);


    //使用宏 for_each_possible_cpu()
    int cpu;
    for_each_possible_cpu(cpu){
        printk("possible cpu-%d &my_birthday=%p\n", cpu, &per_cpu(my_birthday, cpu));
        put_cpu_var(my_birthday);
    }


    //在 <mm/percpu.c> 中定义的全局变量 pcpu_base_addr 被内核源码 EXPORT_SYMBOL 把符号印出来
    //pcpu_base_addr 是在内核启动阶段执行 setup_percpu_areas() 函数时把内核镜像的 ".data..percpu" section
    //区的静态 per-CPU 变量复制拷贝为每个CPU创建静态per-CPU变量的副本。
    //pcpu_base_addr指向变量副本空间的起始地址
    // 
    // 这里打印下 pcpu_base_addr 
    //
    printk("kernel per-CPU pcpu_base_addr=0x%08x\n", pcpu_base_addr); 

    return 0;
}

void my_percpu_exit(void)
{
    printk("my_percpu_init() exit\n");

}

module_init(my_percpu_init);
module_exit(my_percpu_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("dimon.chen 1181302388@qq.com");
MODULE_DESCRIPTION("test module for kernel per-CPU variable");
